<?php
// +----------------------------------------------------------------------
// | KITEGO-Admin「开箱即用」「人人全栈」
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2024 https://www.kitego.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed KITEGO并不是自由软件，未经许可不能去掉KITEGO相关版权
// +----------------------------------------------------------------------
// | Author: KITEGO Team <bd@kitego.cn>
// +----------------------------------------------------------------------

namespace kitego\middleware;

use app\dao\maintenance\RequestLogDao;
use kitego\enums\ClientTypeEnum;
use kitego\utils\SnowflakeUtil;

class RequestLogMiddleware
{
    // 存储请求开始时间的常量
    const REQUEST_START_TIME = '_KITEGO_REQUEST_START_TIME';
    const REQUEST_START_MEMORY = '_KITEGO_REQUEST_START_MEMORY';

    /**
     * 这种方案可以统计从 Web 服务器接收到请求到 PHP 返回响应的完整周期，包括：
     * Web 服务器处理时间（如 Nginx/Apache 的请求处理）
     * PHP-FPM 进程调度时间
     * 框架初始化时间
     * 路由解析时间
     * 所有中间件执行时间
     * 控制器和业务逻辑执行时间
     * 视图渲染时间
     */
    public function handle($request, \Closure $next)
    {
        // 获取全局记录的请求开始时间和内存（在index.php中设置）
        $startTime = defined(self::REQUEST_START_TIME) ? constant(self::REQUEST_START_TIME) : microtime(true);
        $startMemory = defined(self::REQUEST_START_MEMORY) ? constant(self::REQUEST_START_MEMORY) : memory_get_usage();

        // 处理请求
        $response = $next($request);

        // 计算请求处理时间和内存占用
        $endTime = microtime(true);
        $endMemory = memory_get_usage();
        $processTime = round(($endTime - $startTime) * 1000, 2); // 毫秒
        $memoryUsage = round(($endMemory - $startMemory) / 1024, 2); // KB

        // 获取基本请求信息
        $logData = [
            'request_id' => SnowflakeUtil::makeSnowflakeId(),
            'method' => $request->method(),
            'url' => $request->url(),
            'full_url' => $request->url(true),
            'ip' => $request->ip(),
            'status_code' => $response->getCode(),
            'user_agent' => $request->header('user-agent', ''),
            'referer' => $request->header('referer', ''),
            'params' => $this->formatParams($request->param()),
            'header' => $this->formatHeader($request->header()),
            'process_time' => $processTime,
            'memory_usage' => $memoryUsage,
            'created_at' => date('Y-m-d H:i:s'),
            'client_type' => $this->detectClientType($request),
        ];

        // 记录响应数据（大型响应可省略）
        if (config('request_log.record_response', false)) {
            $logData['response'] = $this->formatResponse($response);
        }

        // 记录用户信息（如果已登录）
        $logData['user_id'] = $request->userId ?? 0;
        $logData['user_uuid'] = $request->userUuid ?? 0;
        $logData['nickname'] = $request->nickname ?? '';

        // 保存日志到数据库
        try {
            app()->make(RequestLogDao::class)->save($logData);

            // 慢请求告警
            if ($processTime > config('request_log.slow_request_threshold', 1000)) {
                kg_file_put_contents('request_log_middleware', [
                    "慢请求警告: {$logData['full_url']}, 耗时: {$processTime}ms",
                    var_export($logData, 1)
                ]);
            }

            // 异常状态码告警
            if ($logData['status_code'] >= 500) {
                kg_file_put_contents('request_log_middleware', [
                    "服务器错误: {$logData['full_url']}, 状态码: {$logData['status_code']}",
                    var_export($logData, 1)
                ]);
            }
        } catch (\Exception $e) {
            kg_file_put_contents('request_log_middleware', [
                "服务器错误: {$logData['full_url']}, 状态码: {$logData['status_code']}",
                "请求日志记录失败: " . $e->getLine() . $e->getMessage()
            ]);
        }

        // 将request_id添加到响应头，便于跟踪
        $response->header([
            'X-Request-Id' => $logData['request_id']
        ]);

        return $response;
    }

    /**
     * 格式化请求参数
     */
    private function formatParams($params)
    {
        // 过滤敏感信息
        $sensitiveFields = config('request_log.sensitive_fields', ['password', 'token', 'secret']);
        foreach ($sensitiveFields as $field) {
            if (isset($params[$field])) {
                $params[$field] = '***';
            }
        }

        return json_encode($params, JSON_UNESCAPED_UNICODE);
    }

    /**
     * 格式化请求头
     */
    private function formatHeader($header)
    {
        // 只记录需要的头部信息，避免日志过大
        $keepHeaders = config('request_log.keep_headers', [
            'accept', 'content-type', 'authorization', 'user-agent', 'referer'
        ]);

        $result = [];
        foreach ($header as $key => $value) {
            if (in_array($key, $keepHeaders)) {
                // 处理授权信息，只保留前几位
                if ($key === 'authorization' && strlen($value) > 20) {
                    $value = substr($value, 0, 20) . '***';
                }
                $result[$key] = $value;
            }
        }

        return json_encode($result, JSON_UNESCAPED_UNICODE);
    }

    /**
     * 格式化响应数据
     */
    private function formatResponse($response)
    {
        $content = $response->getContent();

        // 限制响应日志长度
        $maxLength = config('request_log.max_response_length', 2000);
        if (strlen($content) > $maxLength) {
            $content = substr($content, 0, $maxLength) . '...[响应内容过长，已截断]';
        }

        return $content;
    }

    /**
     * 检测客户端类型
     */
    private function detectClientType($request)
    {
        // 检查请求头中的客户端类型标识
        $clientTypeHeader = $request->header('X-Client-Type');
        if (ClientTypeEnum::isValid($clientTypeHeader)) {
            return (int)$clientTypeHeader;
        }

        // 检查请求路径前缀
        return ClientTypeEnum::fromPath($request->root());
    }
}
